package com.gourd.common.rbac.service.impl;

import com.gourd.common.annotation.TargetDataSource;
import com.gourd.common.constant.CommonConstant;
import com.gourd.common.data.UserToken;
import com.gourd.common.exception.ServiceException;
import com.gourd.common.rbac.dto.RbacUserDTO;
import com.gourd.common.rbac.entity.RbacUser;
import com.gourd.common.rbac.service.AuthService;
import com.gourd.common.rbac.service.RbacUserService;
import com.gourd.common.rbac.vo.JwtUser;
import com.gourd.common.rbac.vo.UserVO;
import com.gourd.common.utils.JwtUtil;
import com.gourd.common.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author: gourd
 * createAt: 2018/9/17
 */
@Service
@Slf4j
@Transactional(rollbackFor = Exception.class)
@RefreshScope
public class AuthServiceImpl implements AuthService {
    private final AuthenticationManager authenticationManager;
    private final UserDetailsService userDetailsService;
    private final JwtUtil jwtTokenUtil;
    private final RedisUtil redisUtil;
    private final RbacUserService rbacUserService;

    @Autowired
    public AuthServiceImpl(AuthenticationManager authenticationManager, @Qualifier("userDetailsService") UserDetailsService userDetailsService,
                           JwtUtil jwtTokenUtil, RbacUserService rbacUserService,RedisUtil redisUtil ) {
        this.authenticationManager = authenticationManager;
        this.userDetailsService = userDetailsService;
        this.jwtTokenUtil = jwtTokenUtil;
        this.redisUtil = redisUtil;
        this.rbacUserService = rbacUserService;
    }

    @Override
    public UserVO register(RbacUserDTO rbacUserDTO) {
        UserVO userDetail = new UserVO();
        RbacUser rbacUser = rbacUserService.register(rbacUserDTO);
        BeanUtils.copyProperties(rbacUser,userDetail);
        return userDetail;
    }

    @Override
    @TargetDataSource(CommonConstant.SLAVE_DATASOURCE)
    public UserToken login(String username, String password) {
        //用户验证
        final Authentication authentication = authenticate(username, password);
        //存储认证信息
        SecurityContextHolder.getContext().setAuthentication(authentication);
        //生成token
        final JwtUser userDetail = (JwtUser) authentication.getPrincipal();
        final String token = jwtTokenUtil.generateAccessToken(userDetail);
        return new UserToken(token, userDetail);

    }

    @Override
    public void logout(String token) {
        String userName = jwtTokenUtil.getUsernameFromToken(token);
        jwtTokenUtil.deleteToken(userName);
        redisUtil.del(token);
    }

    @Override
    public UserToken refresh(String token) {
        String username = jwtTokenUtil.getUsernameFromToken(token);
        JwtUser userDetail = (JwtUser) userDetailsService.loadUserByUsername(username);
        if (jwtTokenUtil.canTokenBeRefreshed(token, userDetail.getLastPasswordResetDate())){
            token =  jwtTokenUtil.refreshToken(token);
            return new UserToken(token, userDetail);
        }
        return null;
    }

    @Override
    public JwtUser getUserByToken(String token) {
        return jwtTokenUtil.getUserFromToken(token);
    }


    private Authentication authenticate(String username, String password) {
        try {
            //该方法会去调用userDetailsService.loadUserByUsername()去验证用户名和密码，如果正确，则存储该用户名密码到“security 的 context中”
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        } catch (DisabledException | BadCredentialsException e) {
            throw new ServiceException("用户名或密码无效");
        }
    }
}
